Skip to content

feat: rework tool calling#753

Open
maksymbuleshnyi wants to merge 18 commits into
mainfrom
feat/rework-tool-calling
Open

feat: rework tool calling#753
maksymbuleshnyi wants to merge 18 commits into
mainfrom
feat/rework-tool-calling

Conversation

@maksymbuleshnyi
Copy link
Copy Markdown
Contributor

@maksymbuleshnyi maksymbuleshnyi commented May 26, 2026

Note

High Risk
Changes core agent LLM invocation, tool schema shaping, and parsing across OpenAI/Anthropic/LiteLLM; regressions could break tool calls, streaming, or provider compatibility.

Overview
This PR reworks function-calling and strict tool schemas end-to-end: generation is simplified (no global strict on agent schemas), while OpenAI and Anthropic LLM nodes optionally apply provider-specific strict transforms via new strict_tools on BaseLLM, invoked from completion param building.

Agent parsing now normalizes model output before tool validation: _coerce_json_fields (including nested models) turns JSON-string dict fields back into objects; _strip_protocol_nulls drops null for non-nullable fields so OpenAI strict “use default” semantics match Pydantic. Truncated tool arguments use repair_truncated_json. In function-calling mode with tools, tool_choice: required is set when the caller did not override it.

Anthropic adds strict schema cleaning, a 20-tool cap, and a LiteLLM monkey-patch to forward strict. Streaming emit helpers return completion booleans; integration tests guard answer text vs action_input wrappers and compare streamed tool input with null keys pruned. XML ReAct instructions tighten single-turn behavior. Tests cover nested dict coercion and richer tool schemas.

Reviewed by Cursor Bugbot for commit 02f9b48. Bugbot is set up for automated code reviews on this repo. Configure here.

@maksymbuleshnyi maksymbuleshnyi requested a review from a team as a code owner May 26, 2026 20:26
Comment thread dynamiq/nodes/agents/agent.py Outdated
Comment thread dynamiq/nodes/llms/anthropic.py Outdated
Comment thread dynamiq/nodes/llms/anthropic.py Outdated
Comment thread dynamiq/nodes/agents/agent.py Outdated
Comment thread dynamiq/nodes/agents/agent.py Outdated
Comment thread dynamiq/nodes/llms/openai.py
Comment thread dynamiq/nodes/llms/openai.py
Comment thread dynamiq/nodes/llms/openai.py
Comment thread dynamiq/nodes/llms/openai.py
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 28, 2026

Coverage

Coverage Report •
FileStmtsMissCoverMissing
dynamiq/callbacks
   streaming.py6516290%22, 189, 202–203, 211–212, 228–229, 267–268, 276, 303–309, 313, 321–326, 334, 342–343, 395, 399, 403, 445, 448, 502, 566, 570, 623, 663–666, 694–695, 778–781, 894, 896, 929–930, 1016, 1043, 1149–1154, 1190, 1194, 1225
dynamiq/nodes/agents
   agent.py95416482%83–87, 330–335, 341, 346, 353, 359, 380, 388–389, 413, 418–425, 440–443, 448, 464–465, 483–484, 506, 513–514, 521–522, 528–529, 531, 590–592, 802–804, 819, 828, 854, 870, 889, 902–905, 907, 933–934, 936, 956, 968, 973, 990–991, 1001, 1004, 1014, 1054–1056, 1062–1064, 1173, 1178, 1229, 1263, 1265, 1280–1281, 1288, 1291, 1318–1319, 1335, 1374, 1401–1405, 1472, 1478, 1482, 1485, 1519, 1547, 1550, 1555, 1571, 1645–1646, 1681, 1686, 1710, 1766, 1788–1790, 1793–1794, 1804, 1807, 1817, 1824, 1874–1877, 1897–1898, 1902, 1904–1906, 1908, 1910, 1943, 1951–1952, 1954, 1957, 1960, 2011–2014, 2030, 2039, 2104, 2110–2116, 2122, 2124–2133, 2135–2136
dynamiq/nodes/agents/components
   schema_generator.py1643876%61, 64, 139, 249, 253, 258–263, 266–268, 273, 277–278, 280–288, 290, 305, 307, 318–319, 353–354, 356, 361–362, 398, 415
dynamiq/nodes/llms
   anthropic.py1449236%32–34, 38–39, 43–44, 47, 50–57, 59–60, 118–119, 121–125, 130–134, 137–142, 147–155, 158–160, 162, 166–171, 173–175, 177, 217, 227–233, 240, 242, 244, 284–285, 287–306
   base.py4164589%34, 36, 153, 159, 351, 397–400, 409–415, 442, 488, 531–532, 536, 634–643, 664–665, 680, 718, 720, 754, 756, 823, 886, 985, 1061, 1064, 1095–1096
   openai.py1729942%43–54, 69–70, 72–81, 83–91, 96–100, 102, 105–107, 109–110, 115–116, 120–135, 153–156, 158–165, 167, 173–175, 177–179, 300–312, 317–321
dynamiq/utils
   json_parser.py1344070%28–29, 47, 90–91, 135–136, 201–202, 204–206, 208–224, 226–228, 230–234, 236–237, 239
TOTAL32828970970% 

Tests Skipped Failures Errors Time
2376 2 💤 0 ❌ 0 🔥 2m 44s ⏱️

@maksymbuleshnyi maksymbuleshnyi added the run-integration-tests-with-creds Trigger integration tests with credentials (optional) label May 28, 2026
@tyaroshko tyaroshko added run-integration-tests-with-creds Trigger integration tests with credentials (optional) and removed run-integration-tests-with-creds Trigger integration tests with credentials (optional) labels May 29, 2026
Comment thread dynamiq/nodes/llms/openai.py
Comment thread dynamiq/nodes/llms/openai.py
Comment thread dynamiq/nodes/agents/agent.py
Comment thread dynamiq/callbacks/streaming.py Outdated
Comment thread dynamiq/nodes/agents/agent.py Outdated
@maksymbuleshnyi maksymbuleshnyi added run-integration-tests-with-creds Trigger integration tests with credentials (optional) and removed run-integration-tests-with-creds Trigger integration tests with credentials (optional) labels May 29, 2026
@maksymbuleshnyi maksymbuleshnyi removed the run-integration-tests-with-creds Trigger integration tests with credentials (optional) label May 29, 2026
Comment thread dynamiq/nodes/agents/agent.py Outdated
@maksymbuleshnyi maksymbuleshnyi added the run-integration-tests-with-creds Trigger integration tests with credentials (optional) label May 29, 2026
Comment thread dynamiq/callbacks/streaming.py Outdated
Comment thread dynamiq/nodes/agents/agent.py Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7c7a0aa. Configure here.

Comment thread dynamiq/nodes/agents/components/schema_generator.py
@maksymbuleshnyi maksymbuleshnyi added run-integration-tests-with-creds Trigger integration tests with credentials (optional) and removed run-integration-tests-with-creds Trigger integration tests with credentials (optional) labels Jun 1, 2026
Copy link
Copy Markdown
Contributor

@olbychos olbychos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks solid. Let’s consider better patterns and rely on tests plus Anthropic/Bedrock and other provider checks.

self._strip_nulls_for_fields(tool.input_schema.model_fields, action_input)
return action_input

def _strip_nulls_for_fields(self, fields: Mapping[str, Any], data: Any) -> None:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let clean/rework 'matreshka' code


try:
native_parallel = self.parallel_tool_calls_enabled and self.inference_mode == InferenceMode.FUNCTION_CALLING
# In FUNCTION_CALLING mode with tools present, force a tool call so
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why force tool call?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because this is purpose of function calling inference mode for agent. Otherwise it may not call a tool, and there is no point of such iteration then.


# JSON Schema keywords Anthropic's strict-mode compiler rejects. Strip them
# before sending; keep semantics in the description.
_ANTHROPIC_STRICT_UNSUPPORTED_KEYWORDS = frozenset(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there official source ? looks bad

default=None,
description="Configuration for fallback behavior including the fallback LLM.",
)
strict_tools: bool | list[str] = Field(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have bias towards openai and anthropic/ then how about bedrock and anthropic models? gemini models? grok, etc?

in_string = False
escape = False

for ch in raw:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same 'matreshka' processing; maybe reconsider utils

or reuse

from pydantic_core import from_json

def parse_partial_json(raw: str):
    return from_json(raw, allow_partial=True)

or

from partial_json_parser import ensure_json

def repair_truncated_json(raw: str) -> str:
    return ensure_json(raw)

must recurse into the sub-model and parse it back -- otherwise the string survives
and the nested model's Pydantic validation rejects it.
"""
from typing import Any, ClassVar, Literal
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imports at the top

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-integration-tests-with-creds Trigger integration tests with credentials (optional)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants